import * as d3 from 'd3'
export class SunburstChart {
  divId = ''
  data = []
  chart = {}
  itemsName = []
  color = d3.scaleOrdinal(d3.schemeCategory20)
  radius = 0

  constructor (divId) {
    this.divId = divId
  }

  draw () {
    this.initChart()
    this.initScale()
    this.drawLegend()
    this.drawsunburst()
  }

  initChart () {
    this.chart.padding = {
      left: 50,
      right: 10,
      top: 50,
      bottom: 20
    }
    this.chart.divHeight = document.getElementById(this.divId).offsetHeight -
      this.chart.padding.top -
      this.chart.padding.bottom
    this.chart.divWidth = document.getElementById(this.divId).offsetWidth -
      this.chart.padding.left -
      this.chart.padding.right

    this.chart.svg = d3
      .select('#' + this.divId)
      .append('svg')
      .attr('width', this.chart.divWidth +
        this.chart.padding.left +
        this.chart.padding.right
      )
      .attr('height', this.chart.divHeight +
        this.chart.padding.top +
        this.chart.padding.bottom
      )
      .attr('class', 'svg_chart')

    this.chart.sunburst = this.chart.svg
      .append('g')
      .attr('transform', 'translate(' + this.chart.divWidth * 0.5 + ',' + (this.chart.divHeight * 0.5) + ')')
    this.chart.svg.append('g')
      .attr('class', 'breadcrumb')
  }

  initScale () {
    this.radius = (Math.min(this.chart.divWidth, this.chart.divHeight) / 2) - 10
    this.chart.x = d3.scaleLinear()
      .range([0, 2 * Math.PI])

    this.chart.y = d3.scaleSqrt()
      .range([0, this.radius])

    this.chart.arc = d3.arc()
      .startAngle(d => {
        return Math.max(0, Math.min(2 * Math.PI, this.chart.x(d.x0)))
      })
      .endAngle(d => {
        return Math.max(0, Math.min(2 * Math.PI, this.chart.x(d.x1)))
      })
      .innerRadius(d => {
        return Math.max(0, this.chart.y(d.y0))
      })
      .outerRadius(d => {
        return Math.max(0, this.chart.y(d.y1))
      })
    this.chart.partition = d3.partition()
  }

  drawsunburst () {
    let x = this.chart.x
    let y = this.chart.y
    let arc = this.chart.arc
    let radius = this.radius
    let svg = this.chart.sunburst
    let drawBreadcrumb = this.drawBreadcrumb

    let root = d3.hierarchy(this.data)
    root.sum(function (d) {
      return d.size
    })
    let arcs = svg.selectAll('.arc')
      .data(this.chart.partition(root).descendants())
      .enter()
      .append('g')
      .attr('class', 'arc')
      .on('click', click)
      .on('mouseover', arcMouseOver)
      .on('mouseout', arcMouseOut)

    arcs.append('path')
      .attr('d', this.chart.arc)
      .attr('class', 'mainArc')
      .style('fill', d => {
        d.isShow = 0
        d.color = this.color((d.children ? d : d.parent).data.name)
        return this.color((d.children ? d : d.parent).data.name)
      })
      .style('stroke', 'white')

    arcs.append('path')
      .attr('d', middleArcLine)
      .attr('id', (_, i) => 'hidden-' + i)
      .attr('class', 'hiddenArc')
      .style('fill', 'none')

    let text = arcs.append('text')
      .attr('display', d => textFits(d) ? null : 'none')

    text.append('textPath')
      .attr('startOffset', '20%')
      .attr('xlink:href', (_, i) => '#hidden-' + i)
      .style('font-size', 12)
      .style('fill', 'none')
      .style('stroke', '#fff')
      .style('stroke-width', 5)
      .style('stroke-linejoin', 'round')
      .text(d => d.data.name)

    text.append('textPath')
      .attr('startOffset', '20%')
      .attr('xlink:href', (_, i) => {
        return '#hidden-' + i
      })
      .style('font-size', 12)
      .text(d => d.data.name)

    function click (d) {
      let t = svg.transition()
        .duration(750)
        .tween('scale', function () {
          let xd = d3.interpolate(x.domain(), [d.x0, d.x1])
          let yd = d3.interpolate(y.domain(), [d.y0, 1])
          let yr = d3.interpolate(y.range(), [d.y0 ? 20 : 0, radius])
          return function (t) {
            x.domain(xd(t))
            y.domain(yd(t)).range(yr(t))
          }
        })

      t.selectAll('path.mainArc')
        .attrTween('d', function (d) {
          return function () {
            return arc(d)
          }
        })
      t.selectAll('path.hiddenArc')
        .attrTween('d', function (d) {
          return function () {
            return middleArcLine(d)
          }
        })
      t.selectAll('text')
        .attrTween('display', function (d) {
          return function () {
            return textFits(d) ? null : 'none'
          }
        })
    }

    function arcMouseOver (d) {
      d.isShow = 1
      if (d.depth === 0) {
        let selectNode = []
        svg.selectAll('path.mainArc')
          .filter(function (o) {
            if (o.isShow === 1) {
              selectNode.push(o)
            }
            return o.isShow !== 1
          })
          .style('opacity', 0.3)
        drawBreadcrumb(selectNode)
        return 0
      } else {
        let parent = d.parent
        svg.selectAll('path')
          .each(o => {
            if (o === parent) {
              o.isShow = 1
            }
          })
        arcMouseOver(parent)
      }
    }

    function arcMouseOut (d) {
      svg.selectAll('path')
        .style('opacity', o => {
          o.isShow = 0
          return 1
        })
      d3.select('.svg_chart').select('.breadcrumb')
        .selectAll('.trail')
        .remove()
    }

    function textFits (d) {
      const CHAR_SPACE = 6
      let startAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x0)))
      let endAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x1)))
      let deltaAngle = endAngle - startAngle
      let r = Math.max(0, (y(d.y0) + y(d.y1)) / 2)
      let perimeter = r * deltaAngle
      return d.data.name.length * CHAR_SPACE < perimeter
    }

    function middleArcLine (d) {
      let halfPi = Math.PI / 2
      let angles = [x(d.x0) - halfPi, x(d.x1) - halfPi]
      let r = Math.max(0, (y(d.y0) + y(d.y1)) / 2)

      let middleAngle = (angles[1] + angles[0]) / 2
      let invertDirection = middleAngle > 0 && middleAngle < Math.PI
      if (invertDirection) { angles.reverse() }

      let path = d3.path()
      path.arc(0, 0, r, angles[0], angles[1], invertDirection)
      return path.toString()
    }
  }

  drawBreadcrumb (data) {
    let b = {
      width: 75,
      height: 30,
      spacing: 3,
      tail: 10
    }

    let trail = d3.select('.svg_chart').select('.breadcrumb')
    trail = trail.selectAll('.trail')
      .data(data, d => {
        return d.data.name + d.depth
      })

    let trailEnter = trail.enter()
      .append('svg:g')
      .attr('class', 'trail')

    trailEnter.append('svg:polygon')
      .attr('points', breadcrumbPoints)
      .style('fill', d => d.color)

    trailEnter.merge(trail)
      .attr('transform', function (d, i) {
        return 'translate(' + i * (b.width + b.spacing) + ',0)'
      })

    trail.exit().remove()

    function breadcrumbPoints (d, i) {
      let points = []
      points.push('0,0')
      points.push(b.width + ',0')
      points.push(b.width + b.tail + ',' + (b.height / 2))
      points.push(b.width + ',' + b.height)
      points.push('0,' + b.height)
      if (i > 0) {
        points.push(b.tail + ',' + (b.height / 2))
      }
      return points.join(' ')
    }
  }

  drawLabel () {

  }

  drawLegend () {
    let legendWidth = 15
    let legendHeight = 15
    let legends = this.chart.svg
      .selectAll('.legend')
      .data(this.itemsName)
    let legendEnter = legends.enter()
      .append('g')
      .attr('class', 'legend')

    legendEnter.append('rect')
      .attr('x', this.chart.divWidth + 5)
      .attr('y', (d, i) => this.chart.padding.top + legendHeight * i)
      .attr('width', 0)
      .attr('height', legendHeight - 5)
      .style('fill', (d, i) => this.color(i))

    legendEnter.append('text')
      .attr('x', this.chart.divWidth + legendWidth * 1.3 + 5)
      .attr('y', (d, i) => this.chart.padding.top + legendHeight * i + legendHeight / 2)
      .text(String)
      .style('font-size', 1e-6)

    let legendUpdate = legendEnter.merge(legends)

    legendUpdate.select('rect')
      .transition()
      .duration(750)
      .attr('width', legendWidth)

    legendUpdate.select('text')
      .transition()
      .duration(750)
      .style('font-size', 10)

    let legendExit = legends.exit()
      .transition()
      .duration(750)
      .remove()

    legendExit.select('rect')
      .attr('width', 0)

    legendExit.select('text')
      .style('stroke-opacity', 1e-6)
  }
}
